<?php

namespace App\Admin;

use App\Admin\Exception\Handle;
use App\Admin\Grid\Column;
use App\Admin\Grid\Displayers\Actions;
use App\Admin\Grid\Displayers\RowSelector;
use App\Admin\Grid\Exporter;
use App\Admin\Grid\Filter;
use App\Admin\Grid\Row;
use App\Admin\Grid\Tools;
use App\Admin\Grid\Tools\BatchActions;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Collection;
use App\Admin\Grid\Model;

class Grid
{
    /**
     * 表格数据模型实例
     *
     * @var Model
     */
    protected $model;

    /**
     * 表格的所有列
     *
     * @var Collection
     */
    protected $columns;

    /**
     * 数据库表的所有字段
     *
     * @var Collection
     */
    protected $dbColumns;

    /**
     * 表格的所有行
     *
     * @var Collection
     */
    protected $rows;

    /**
     * 表格行的回调函数
     *
     * @var \Closure
     */
    protected $rowsCallback;

    /**
     * 表格的所有列的名字
     *
     * @var array
     */
    public $columnNames = [];

    /**
     * 构造表格的函数
     *
     * @var \Closure
     */
    protected $builder;

    /**
     * 表格是否已构建
     *
     * @var bool
     */
    protected $built = false;

    /**
     * 表格blade模板需要的所有参数
     *
     * @var array
     */
    protected $variables = [];

    /**
     * 表格的筛选器
     *
     * @var Filter
     */
    protected $filter;

    /**
     * 表格的资源的url路径
     *
     * @var
     */
    protected $resourcePath;

    /**
     * 默认的数据库表的主键
     *
     * @var string
     */
    protected $keyName = 'id';

    /**
     * 导出类
     *
     * @var
     */
    protected $exporter;

    /**
     * 表格采用的默认的blade模板
     *
     * @var string
     */
    protected $view = 'admin.grid.table';

    /**
     * 前端可选的每页数量
     *
     * @var array
     */
    public $perPages = [10, 15, 20, 30, 50, 100];

    /**
     * 表格默认的每页数
     *
     * @var int
     */
    public $perPage = 15;

    /**
     * 表格顶部的按钮
     *
     * @var Tools
     */
    public $tools;

    /**
     * 表格"操作"列的回调
     *
     * @var \Closure
     */
    protected $actionsCallback;

    /**
     * 是否已经执行过表格的builder回调
     *
     * @var bool
     */
    protected $calledBuilder = false;

    /**
     * 表格的一些设置
     *
     * @var array
     */
    protected $options = [
        // 分页
        'usePagination'    => true,
        // 查询过滤
        'useFilter'        => true,
        // 导出功能
        'useExporter'      => true,
        // 操作按钮列
        'useActions'       => true,
        // 复选框列
        'useRowSelector'   => true,
        // 添加按钮
        'allowCreate'      => true,
        // 批量删除
        'allowBatchDelete' => true,
    ];

    /**
     * Grid constructor.
     * @param EloquentModel $model
     * @param \Closure      $builder
     */
    public function __construct(EloquentModel $model, \Closure $builder)
    {
        $this->keyName = $model->getKeyName();
        $this->model = new Model($model);
        $this->columns = new Collection();
        $this->rows = new Collection();
        $this->builder = $builder;

        // TODO 先放这里,之后会放到统一的中间件或者服务提供者里
        static::registerColumnDisplayer();

        $this->setDbColumns();
        $this->setupTools();
        $this->setupFilter();
        $this->setupExporter();
    }

    /**
     * 获取模型的主键名
     *
     * @return string
     */
    public function getKeyName()
    {
        return $this->keyName ?: 'id';
    }

    /**
     * 执行表格的builder回调
     */
    protected function callBuilder()
    {
        if ($this->calledBuilder) {
            return;
        }
        call_user_func($this->builder, $this);

        $this->calledBuilder = true;
    }

    /**
     * 设置表格数据来源的数据库表的所有列名
     */
    protected function setDbColumns()
    {
        $connections = $this->model()->eloquent()->getConnectionName();

        $this->dbColumns = collect(
            \Schema::connection($connections)
                   ->getColumnListing($this->model()->getTable())
        );
    }

    /**
     * 设置过滤器
     */
    protected function setupFilter()
    {
        $this->filter = new Filter($this->model());
    }

    /**
     * 设置导出器
     */
    protected function setupExporter()
    {
        $this->callBuilder();
        if (
            ($scope = \Request::get(Exporter::$queryName)) &&
            $this->allowExport()
        ) {
            (new Exporter($this))->resolve($this->exporter)->withScope($scope)->export();
        }
    }

    /**
     * 设置表格的顶部按钮
     */
    public function setupTools()
    {
        $this->tools = new Tools($this);
    }

    /**
     * 返回表格Model实例
     *
     * @return Model
     */
    public function model()
    {
        return $this->model;
    }

    /**
     * 生成字符串
     *
     * @return string
     * @throws \Throwable
     */
    public function __toString()
    {
        return $this->render();
    }

    /**
     * 生成html代码
     *
     * @return string
     * @throws \Throwable
     */
    public function render()
    {
        try {
            $this->build();
        } catch (\Exception $e) {
            return with(new Handle($e))->render();
        }

        return view($this->view, $this->variables())->render();
    }

    /**
     * 构建表格的html代码
     *
     * @throws \Exception
     */
    public function build()
    {
        if ($this->built) {
            return;
        }

        $data = $this->processFilter();

        $this->prependRowSelectorColumn();
        $this->appendActionsColumn();

        Column::setOriginalGridData($data);

        $this->columns->map(function (Column $column) use (&$data) {
            $data = $column->fill($data);

            $this->columnNames[] = $column->getName();
        });

        $this->buildRows($data);

        $this->built = true;
    }

    /**
     * 把复选框加到表格第一列
     *
     * @return void
     */
    protected function prependRowSelectorColumn()
    {
        if (!$this->option('useRowSelector')) {
            return;
        }

        // 如果没有任何批量操作,并且禁用了导出,则不需要前面的复选框
        if (!$this->tools->hasBatchActions() && !$this->option('useExporter')) {
            return;
        }

        $grid = $this;

        $column = new Column('__row_selector__', RowSelector::$label);
        $column->width(RowSelector::$width);
        $column->setGrid($this);

        $column->display(function ($value) use ($grid, $column) {
            $actions = new RowSelector($value, $grid, $column, $this);

            return $actions->display();
        });

        $this->columns->prepend($column);
    }

    /**
     * 设置或者获得一个配置的值
     *
     * @param string $key
     * @param mixed  $value
     * @return $this|mixed
     */
    public function option($key, $value = null)
    {
        if (is_null($value)) {
            return $this->options[$key];
        }

        $this->options[$key] = $value;

        return $this;
    }

    /**
     * 返回过滤器筛选后的数据
     *
     * @return array
     * @throws \Exception
     */
    public function processFilter()
    {
        $this->callBuilder();
        if ($this->useFilter()) {
            return $this->filter->execute();
        } else {
            return $this->model->buildData();
        }
    }

    /**
     * 返回表格blade模板所需的变量
     *
     * @return array
     */
    protected function variables()
    {
        $this->variables['grid'] = $this;

        return $this->variables;
    }

    /**
     * 用字段名作为方法,添加一列到表格中
     *
     * @param $method
     * @param $arguments
     * @return Column
     */
    public function __call($method, $arguments)
    {
        $label = isset($arguments[0]) ? $arguments[0] : ucfirst($method);

        if ($column = $this->handleGetMutatorColumn($method, $label)) {
            return $column;
        }

        if ($column = $this->handleRelationColumn($method, $label)) {
            return $column;
        }

        if ($column = $this->handleTableColumn($method, $label)) {
            return $column;
        }

        return $this->addColumn($method, $label);
    }

    /**
     * 处理数据库表的列
     *
     * @param string $method
     * @param string $label
     *
     * @return bool|Column
     */
    protected function handleTableColumn($method, $label)
    {
        if ($this->dbColumns->has($method)) {
            return $this->addColumn($method, $label);
        }

        return false;
    }

    /**
     * 处理关联方法
     *
     * @param string $method
     * @param string $label
     *
     * @return bool|Column
     */
    protected function handleRelationColumn($method, $label)
    {
        $model = $this->model()->eloquent();

        $relationName = camel_case($method);

        if (!method_exists($model, $relationName)) {
            return false;
        }

        if (!($relation = $model->$relationName()) instanceof Relation) {
            return false;
        }

        if ($relation instanceof HasOne || $relation instanceof BelongsTo) {
            $this->model()->with($relationName);
            if ($relation->getQuery()->getModel() instanceof \App\Models\File) {
                return $this->addColumn(snake_case($relationName), $label);
            } else {
                return $this->addColumn($method, $label)->setRelation(snake_case($method));
            }
        }

        if (
            $relation instanceof HasMany ||
            $relation instanceof BelongsToMany ||
            $relation instanceof MorphToMany
        ) {
            $this->model()->with($relationName);

            return $this->addColumn(snake_case($relationName), $label);
        }

        return false;
    }

    /**
     * 处理该字段是不是getXyxAttribute类型的
     *
     * @param string $method
     * @param string $label
     *
     * @return bool|Column
     */
    protected function handleGetMutatorColumn($method, $label)
    {
        if ($this->model()->eloquent()->hasGetMutator($method)) {
            return $this->addColumn($method, $label);
        }

        return false;
    }

    /**
     * 向表格中添加一列
     *
     * @param string $column
     * @param string $label
     * @return Column
     */
    protected function addColumn($column = '', $label = '')
    {
        $column = new Column($column, $label);
        $column->setGrid($this);

        return $this->columns[] = $column;
    }

    /**
     * 构建表格的每一行d
     *
     * @param array $data
     */
    protected function buildRows(array $data)
    {
        $this->rows = collect($data)->map(function ($val, $key) {
            $row = new Row($key, $val);

            $row->setKeyName($this->keyName);

            return $row;
        });

        if ($this->rowsCallback) {
            $this->rows->map($this->rowsCallback);
        }
    }

    /**
     * 设置或获取当前资源的url路径
     *
     * @param null $path
     * @return $this|string
     */
    public function resource($path = null)
    {
        if (!empty($path)) {
            $this->resourcePath = $path;

            return $this;
        }

        if (!empty($this->resourcePath)) {
            return $this->resourcePath;
        }

        return \Route::current()->uri();
    }

    /**
     * 返回是否有添加按钮
     *
     * @return bool
     */
    public function allowCreation()
    {
        return $this->option('allowCreate');
    }

    /**
     * 返回是否有导出按钮
     *
     * @return bool
     */
    public function allowExport()
    {
        return $this->option('useExporter');
    }

    /**
     * 批量添加列到表格中
     *
     * @param array $columns
     * @return Collection|void
     */
    public function columns($columns = [])
    {
        if (func_num_args() == 0) {
            return $this->columns;
        }

        if (func_num_args() == 1 && is_array($columns)) {
            foreach ($columns as $column => $label) {
                $this->column($column, $label);
            }

            return;
        }

        foreach (func_get_args() as $column) {
            $this->column($column);
        }
    }

    /**
     * 添加一列到表格中
     *
     * @param        $name
     * @param string $label
     * @return Column
     */
    public function column($name, $label = '')
    {
        $relationName = $relationColumn = '';

        if (strpos($name, '.') !== false) {
            list($relationName, $relationColumn) = explode('.', $name);
            $relationName = camel_case($relationName);

            $relation = $this->model()->eloquent()->$relationName();

            $label = empty($label) ? ucfirst($relationColumn) : $label;

            $name = snake_case($relationName) . '.' . $relationColumn;
        }

        $column = $this->addColumn($name, $label);

        if (isset($relation) && $relation instanceof Relation) {
            $this->model()->with($relationName);
            $column->setRelation($relationName, $relationColumn);
        }

        return $column;
    }

    /**
     * 获取所有行,或者设置行的回调处理函数
     *
     * @param \Closure|null $callable
     * @return Collection
     */
    public function rows(\Closure $callable = null)
    {
        if (is_null($callable)) {
            return $this->rows;
        }

        $this->rowsCallback = $callable;
    }

    /**
     * 返回是否允许批量删除
     *
     * @return Grid|mixed
     */
    public function allowBatchDeletion()
    {
        return $this->option('allowBatchDelete');
    }

    /**
     * 设置表格分页的每页数
     *
     * @param int $perPage
     */
    public function paginate($perPage = 15)
    {
        $this->perPage = $perPage;

        $this->model()->paginate($perPage);
    }

    /**
     * 获取表格的分页器
     *
     * @return mixed
     */
    public function paginator()
    {
        return new Tools\Paginator($this);
    }

    /**
     * 设置表格"操作"列的回调
     *
     * @param \Closure $callback
     * @return $this
     */
    public function actions(\Closure $callback)
    {
        $this->actionsCallback = $callback;

        return $this;
    }

    /**
     * 添加"操作"列
     *
     * @return void
     */
    protected function appendActionsColumn()
    {
        if (!$this->option('useActions')) {
            return;
        }

        $grid = $this;
        $callback = $this->actionsCallback;
        $column = $this->addColumn('__actions__', '操作');

        $column->display(function ($value) use ($grid, $column, $callback) {
            $actions = new Actions($value, $grid, $column, $this);

            return $actions->display($callback);
        });
    }

    /**
     * 禁止所有表格行的操作
     *
     * @return $this
     */
    public function disableActions()
    {
        return $this->option('useActions', false);
    }

    /**
     * 禁用添加按钮
     *
     * @return $this
     */
    public function disableCreation()
    {
        return $this->option('allowCreate', false);
    }

    /**
     * 禁用导出
     *
     * @return $this
     */
    public function disableExport()
    {
        return $this->option('useExporter', false);
    }

    /**
     * 禁用表格的查询过滤
     *
     * @return $this
     */
    public function disableFilter()
    {
        return $this->option('useFilter', false);
    }

    /**
     * 禁用分页
     *
     * @return $this
     */
    public function disablePagination()
    {
        $this->model->disablePaginate();

        return $this->option('usePagination', false);
    }

    /**
     * 禁用行首的复选框
     *
     * @return $this
     */
    public function disableRowSelector()
    {
        $this->tools(function ($tools) {
            /**
             * @var Grid\Tools $tools
             */
            $tools->disableBatchActions();
        });

        return $this->option('useRowSelector', false);
    }

    /**
     * 设置表格的导出器
     *
     * @param $exporter
     *
     * @return $this
     */
    public function exporter($exporter)
    {
        $this->exporter = $exporter;

        return $this;
    }

    /**
     * 导出的下载url地址
     *
     * @param string $scope
     * @param mixed  $args
     * @return string
     */
    public function exportUrl($scope, $args = null)
    {
        $input = \Request::all();

        $input = array_merge($input, Exporter::formatExportQuery($scope, $args));

        return $this->resource() . '?' . http_build_query($input);
    }

    /**
     * 设置查询过滤回调
     *
     * @param \Closure $callback
     */
    public function filter(\Closure $callback)
    {
        call_user_func($callback, $this->filter);
    }

    /**
     * 返回表格查询过滤器
     *
     * @return Filter
     */
    public function getFilter()
    {
        return $this->filter;
    }

    /**
     * 设置表格可选的每页数
     *
     * @param array $perPages
     */
    public function perPages(array $perPages)
    {
        $this->perPages = $perPages;
    }

    /**
     * 注册列数据显示的处理方法
     *
     * @return void.
     */
    public static function registerColumnDisplayer()
    {
        $map = [
            'editable'    => \App\Admin\Grid\Displayers\Editable::class,
            'switch'      => \App\Admin\Grid\Displayers\SwitchDisplay::class,
            'image'       => \App\Admin\Grid\Displayers\Image::class,
            'label'       => \App\Admin\Grid\Displayers\Label::class,
            'link'        => \App\Admin\Grid\Displayers\Link::class,
            'dot'         => \App\Admin\Grid\Displayers\Dot::class,
            'progressBar' => \App\Admin\Grid\Displayers\ProgressBar::class,
            'orderable'   => \App\Admin\Grid\Displayers\Orderable::class,
        ];

        foreach ($map as $abstract => $class) {
            Column::extend($abstract, $class);
        }
    }

    /**
     * 返回添加按钮的html代码
     *
     * @return Tools\CreateButton
     */
    public function renderCreateButton()
    {
        return new Tools\CreateButton($this);
    }

    /**
     * 返回导出按钮的html代码
     *
     * @return Tools\ExportButton
     */
    public function renderExportButton()
    {
        return new Tools\ExportButton($this);
    }

    /**
     * 返回查询过滤的html代码
     *
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|string
     */
    public function renderFilter()
    {
        if (!$this->useFilter()) {
            return '';
        }

        return $this->filter->render();
    }

    /**
     * 返回自定义表格顶部按钮的html代码
     *
     * @return string
     */
    public function renderHeaderTools()
    {
        return $this->tools->render();
    }

    /**
     * 设置表格所用的blade模板
     *
     * @param string $view
     * @param array  $variables
     */
    public function setView($view, $variables = [])
    {
        if (!empty($variables)) {
            $this->with($variables);
        }

        $this->view = $view;
    }

    /**
     * 添加额外的数据到自定义表格blade模板
     *
     * @param array $variables
     *
     * @return $this
     */
    public function with($variables = [])
    {
        $this->variables = $variables;

        return $this;
    }

    /**
     * 设置表格顶部的功能按钮
     *
     * @param \Closure $callback
     *
     * @return void
     */
    public function tools(\Closure $callback)
    {
        call_user_func($callback, $this->tools);
    }

    /**
     * 判断该表格是否使用分页
     *
     * @return bool
     */
    public function usePagination()
    {
        return $this->option('usePagination');
    }

    /**
     * 禁用批量删除
     */
    public function disableBatchDelete()
    {
        $this->tools(function (Tools $tools) {
            $tools->batch(function (BatchActions $actions) {
                $actions->disableDelete();
            });
        });
    }

    /**
     * 返回是否有查询
     *
     * @return bool
     */
    public function hasFilter()
    {
        return $this->useFilter() && (bool)count($this->filter->filters());
    }

    /**
     * 返回是否使用查询过滤
     *
     * @return bool
     */
    public function useFilter()
    {
        return $this->option('useFilter');
    }
}